package org.zyj.io.example.case1;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.EventLoop;

import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;

/**
 * ctx.channel().eventLoop()的使用
 * 通过 io.netty.channel.EventLoop 来执行异步操作
 * 其实 channelRead() 本身也是在 EventLoop中执行的
 *
 * @author zhouyijin
 * @date 2022-01-14 14:48:54
 */
public class NettyServerHandler extends ChannelInboundHandlerAdapter {

    /**
     * 读取客户端发送的数据
     * ---
     * 注意: 代码中的 EventLoop, 不一定是这个channel独享的, 可能这个EventLoop上会有很多channel, 你要是搞耗时任务, 就会阻塞其他的channel
     *
     * @param ctx 上下文对象, 可以拿到管道pipeline, 通道channel, 地址等
     * @param msg 客户端发送的数据, 要根据实际情况转换为其他对象来进行处理
     * @throws Exception
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("server ctx = " + ctx);
        System.out.println("remoteAddress = " + ctx.channel().remoteAddress());

        //1. 读取客户端的内容
        ByteBuf buf = (ByteBuf) msg;
        System.out.println("msg = " + buf.toString(StandardCharsets.UTF_8));

        //        ctx.pipeline().channel().eventLoop().taskQueue;
        //2. 向 EventLoop.taskQueue 中放一个任务, 理解一下taskQueue的工作模式
        //EventLoop实际上就是一个线程,EventLoopGroup就是一个线程池
        ctx.channel().eventLoop().execute(() -> {
            try {
                //第十秒发送内容
                TimeUnit.SECONDS.sleep(1);//客户端1秒后会收到消息
                ctx.writeAndFlush(Unpooled.copiedBuffer("等1秒后发送的内容", StandardCharsets.UTF_8));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        //3. 向 EventLoop.taskQueue 中放一个任务
        ctx.channel().eventLoop().execute(() -> {
            try {
                TimeUnit.SECONDS.sleep(5);//客户端30秒后会收到消息, 因为taskQueue是被一个线程执行的, 要等上一个任务执行完才能执行这一条
                ctx.writeAndFlush(Unpooled.copiedBuffer("等5秒后发送的内容", StandardCharsets.UTF_8));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        //4. 定时任务; EventLoop.scheduledTaskQueue 中放一个任务
        //从这个Runnable提交的那一刻计时, 然后指定时间到了 立刻执行
        //但是如果有其他任务在taskQueue中执行阻塞, 那会等所有taskQueue任务执行完成后, 才会处理scheduledTaskQueue中的任务
        //根据我自己的实验, scheduled 的任务一定是在taskQueue中没有任务的时候才会被执行, 优先级较低
        ctx.channel().eventLoop().schedule(() -> {
            ctx.writeAndFlush(Unpooled.copiedBuffer("scheduled 5秒, 内容1", StandardCharsets.UTF_8));
        }, 5, TimeUnit.SECONDS);//后执行
        ctx.channel().eventLoop().schedule(() -> {
            ctx.writeAndFlush(Unpooled.copiedBuffer("scheduled 2秒, 内容2", StandardCharsets.UTF_8));
        }, 2, TimeUnit.SECONDS);//先执行

        ctx.channel().eventLoop().execute(() -> {
            try {
                TimeUnit.SECONDS.sleep(5);//客户端30秒后会收到消息, 因为taskQueue是被一个线程执行的, 要等上一个任务执行完才能执行这一条
                ctx.writeAndFlush(Unpooled.copiedBuffer("等5秒后发送的内容 taskQueue-3", StandardCharsets.UTF_8));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        //如何获取任务可以参考: io.netty.util.concurrent.SingleThreadEventExecutor.takeTask
    }

    //数据读取完毕
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        final ByteBuf byteBuf = Unpooled.copiedBuffer("channelReadComplete", StandardCharsets.UTF_8);
        //将数据写入到缓存, 并刷新
        ctx.writeAndFlush(byteBuf); // write + flush
    }

    //处理异常, 一般情况是关闭通道
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.close();
    }
}
